package common

import (
	"context"
	"errors"
	"gitee.com/kinwyb/appTools/log"
	"go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/concurrency"
	"time"
)

var (
	lockPrefix = "/"
	LockFail   = errors.New("锁失败")
	UnlockFail = errors.New("解锁失败")
)

//etcd对象
var etcd *clientv3.Client

// InitializeEtcdClient 初始化ETCD连接
func InitializeEtcdClient(endpoints []string) {
	if etcd != nil {
		etcd.SetEndpoints(endpoints...)
	} else {
		var err error
		etcd, err = clientv3.New(clientv3.Config{Endpoints: endpoints})
		if err != nil {
			log.AppTool.WithError(err).Panic("etcd连接失败")
		}
	}
}

// GetEtcdClient 获取etcd连接对象
func GetEtcdClient() *clientv3.Client {
	if etcd == nil {
		InitializeEtcdClient([]string{"127.0.0.1:2379"})
	}
	return etcd
}

// GetEtcdSession 获取etcd会话
func GetEtcdSession() (*concurrency.Session, error) {
	if etcd == nil {
		InitializeEtcdClient([]string{"127.0.0.1:2378"})
	}
	return concurrency.NewSession(etcd)
}

// CloseEtcdClient 关闭etcd对象
func CloseEtcdClient() {
	if etcd != nil {
		etcd.Close()
		log.AppTool.Info("etcd会话关闭")
	}
}

// Lock 锁对象
type Lock struct {
	mux     *concurrency.Mutex
	client  *clientv3.Client
	session *concurrency.Session
}

// NewLock 获取一个分布式锁对象。如果出错会painc需要处理panic情况
func NewLock(path string) (*Lock, error) {
	if etcd == nil {
		return nil, errors.New("ETCD尚未初始化")
	}
	path = lockPrefix + path
	etcdSession, err := concurrency.NewSession(etcd)
	if err != nil {
		return nil, LockFail
	}
	return &Lock{
		session: etcdSession,
		mux:     concurrency.NewMutex(etcdSession, path),
		client:  etcd,
	}, nil
}

// SetLockPrefix 设置锁前缀
func SetLockPrefix(prefix string) {
	lockPrefix = prefix
}

// LockWithErr 加锁如果出错返回错误
func (l *Lock) LockWithErr() error {
	ctx, _ := context.WithTimeout(l.client.Ctx(), 5*time.Second)
	return l.mux.Lock(ctx)
}

// UnlockWithErr 解锁如果出错返回错误
func (l *Lock) UnlockWithErr() error {
	defer l.session.Close() //关闭session
	return l.mux.Unlock(l.client.Ctx())
}

// Lock 加锁
func (l *Lock) Lock() {
	l.mux.Lock(l.client.Ctx())
}

// Unlock 解锁
func (l *Lock) Unlock() {
	defer l.session.Close() //关闭session
	l.mux.Unlock(l.client.Ctx())
}

// CallFunc 回调处理
func (l *Lock) CallFunc(fun func() error) error {
	if fun == nil {
		return nil
	}
	e := l.LockWithErr()
	if e != nil {
		return LockFail
	}
	err := fun()
	if err != nil {
		l.Unlock() //解锁
		return err
	}
	l.Unlock() //解锁
	return nil
}
